

# Required imports

from utils import indentify, sequence, type_valid

# Base error raised when an problem is detecten in am event section
class EventException(Exception):
    
    def __init__(self, error, event):
        Exception.__init__(self) 
        self.event = event; self.error = error
        
    def __str__(self):
        return 'Exception in ' + str(self.event) + '\n: \t' + str(self.error)
    
# Error raised when an exception occurs within condition checking
class ConditionException(Exception):
    
    def __init__(self, error, event):
        Exception.__init__(self) 
        self.cond = cond; self.error = error, self.event = event; 
        
    def __str__():
        return 'Exception in Condition of' + str(self.event) + '\n: \t' + str(self.error)
        

# Defines the new style Event class (replaces Hook)
class Event(object):

    # A list of queued actions pending for execution
    # A None value delimits actions from different events
    action_queue = list()
    locals_queue = list()
    
    # Creates a new event with no actions
    def __init__(self, trigger, cond = "True", owner = None):
        object.__init__(self)
        
        # Set the basic Event attributes
        self.trigger = str(trigger)
        self.condition = str(cond)
        if owner is not None: owner = weakref.proxy(owner) 
        self.owner = owner
        
        # Initialize the actions container
        self.actions = []
        
    # Add a new action to the event
    def parse_action(self, action, clsname):
        try:
            cls = eval('Event.'+clsname)
            self.actions.append(cls(action, self))
        except AttributeError, error: 
            msg = "Invalid event action '" + clsname.lower() + "'"
            raise EventException(msg, self)
      
    # Tests the Event for its condition
    def verify(self, globals, locals):
        try:
            return eval(self.condition, globals, locals)
        except Exception, error:
            msg = "Raised in condition -> " + str(error)
            raise EventException(msg, self)
        
    # Iterates over the Events actions
    def __iter__(self):
        for action in self.actions:
            yield action
        
    # Combines verification and execution queueing into one
    def process(self, exec_globals, exec_locals):
        if self.verify(exec_globals, exec_locals):
            self.enqueue(exec_locals)
                
        
    # String description of Event
    def __str__(self):
        return '< event: ' +  str(self.trigger) + '(' + str(self.condition) + ') >'

    
    # Base class for event actions
    class Action(object):
        def __init__(self, parent):
            self.parent = parent
            
        # Override this method when creating an action
        def run(self, exec_globals, exec_locals): pass

        # Add a single action to the end of the action queue
        def stack(self): Event.action_queue.append(self)

      
    # Defines an action in the form of a python script
    class Exec(Action):
        
        def __init__(self, node, parent):
            Event.Action.__init__(self, parent)
            self.code = indentify(node.text)
            
        # Runs an Exec action within the given context
        def run(self, gdict, ldict):
            try:
                exec self.code in gdict, ldict
            except Exception, error:
                print error, dir(error)
                raise EventException(error, self.parent)
                
            
        # Prints the Exec code
        def __repr__(self): return self.code
        
    # Defines an action in the form of a series of dialogs
    class Dialog(Action):
        
        def __init__(self, node, parent):
            Event.Action.__init__(self, parent)
    
            
    # Defines a branch for the following actions
    class Branch(Action):
        
        def __init__(self, node, parent):
            Event.Action.__init__(self, parent)
                
                
    # Initializes a new Event from a XML element
    @classmethod
    def from_XML(cls, node):
        
        # If node is a sequence, do batch parsing
        if isinstance(node, sequence):
            return [Event.from_XML(i) for i in node]
        
        # The node must have a tag of 'event' and no CDATA
        assert node.tag == "event", "Only an event element can be parsed."
        assert not node.text or node.text.isspace(), "Events may not have CDATA"
        
        # Extract the basic Event attributes
        trigger = node.attrib.get('trigger', node.attrib.get('on', "manual"))
        condition = node.attrib.get('condition', node.attrib.get('if', "True"))

        # Create the new empty event
        event = Event(trigger, condition)
        
        for child in node:
            tag = child.tag.title()
            assert not child.tail or child.tail.isspace(), "Events may not have CDATA"
            event.parse_action(child, tag)
            
        return event

    # Adds this event's actions to the class action queue
    def enqueue(self, exec_locals):
        partial = self.actions[:]
        partial.append(None)
        partial.extend(Event.action_queue)
        Event.action_queue = partial[:]
        exec_locals['trigger'] = self.trigger
        Event.locals_queue.insert(0, exec_locals)

    # Resolve all pending actions from the action queue
    @classmethod
    def resolve(cls, exec_globals):
        while cls.action_queue:
            action = cls.action_queue.pop(0)
            print "Resolving", action
            if action is None:
                cls.locals_queue.pop(0)
            else:
                print cls.locals_queue
                action.run(exec_globals, cls.locals_queue[-1])

    # Cancels an event's worth of actions
    @classmethod
    def cancel(cls):
        while len(cls.action_queue) > 0:
            action = cls.action_queue.pop()
            if action is None: break
        
        
        
# Runs an unit test on the given XML file, loading it as an Event
if __name__ == "__main__":
    import sys
    import xml.etree.ElementTree as xml
    
    assert len(sys.argv) >= 2, "Insuficient arguments for test."
    tree = xml.parse(sys.argv[1])
    
    first = True
  
    for node in tree.findall('event'):    
        event = Event.from_XML(node)
        for action in event.process(globals()):  
            action.run(globals(), locals())




        
